/* var_info.cpp */

#include "var_info.h"
#include "mm5_file.h"
#include "sub_header.h"

namespace dodomet
{
namespace mm5
{
// Infolet_Impl

Infolet_Impl::Infolet_Impl()
	:
	use_count_(1)
{
}

Infolet_Impl::Infolet_Impl(const MM5_File& mm5_file, const Sub_Header& sub_header)
	: filename_(mm5_file.filename()),
	start_time_(mm5_file.start_time()),
	xtime_(sub_header.xtime()),
	field_pos_(sub_header.field_pos()),
	use_count_(1)
{
}

Infolet_Impl::Infolet_Impl(const std::string& filename, const boost::posix_time::ptime& start_time, const Sub_Header& sub_header)
	: filename_(filename),
	start_time_(start_time),
	xtime_(sub_header.xtime()),
	field_pos_(sub_header.field_pos()),
	use_count_(1)
{
}

Infolet_Impl::Infolet_Impl(const Infolet_Impl& orig)
	:
	filename_(orig.filename_),
	start_time_(orig.start_time_),
	xtime_(orig.xtime_),
	field_pos_(orig.field_pos_),
	use_count_(1)
{
}

std::string Infolet_Impl::filename() const
{
	return filename_;
}

boost::posix_time::ptime Infolet_Impl::start_time() const
{
	return start_time_;
}

boost::posix_time::time_duration Infolet_Impl::xtime() const
{
	return xtime_;
}

fortran_real Infolet_Impl::xtime_real() const
{
	return fortran_real(xtime_.total_seconds() / 60.0);
}

std::streampos Infolet_Impl::field_pos() const
{
	return field_pos_;
}

// Infolet
Infolet::Infolet()
	: impl_(new Infolet_Impl())
{
}

Infolet::Infolet(const MM5_File& mm5_file, const Sub_Header& sub_header)
	: impl_(new Infolet_Impl(mm5_file, sub_header))
{
}

Infolet::Infolet(const std::string& filename, const boost::posix_time::ptime& start_time, const Sub_Header& sub_header)
	: impl_(new Infolet_Impl(filename, start_time, sub_header))
{
}

Infolet::Infolet(const Infolet& orig)
	: impl_(orig.impl_)
{
	++impl_->use_count_;
}

Infolet::~Infolet()
{
	if(--impl_->use_count_ == 0)
		delete impl_;
}

Infolet& Infolet::operator= (const Infolet& orig)
{
	++orig.impl_->use_count_;
	if(--impl_->use_count_ == 0)
		delete impl_;
	impl_ = orig.impl_;
}

void Infolet::on_write()
{
	if(impl_->use_count_ == 1)
		return;
	Infolet_Impl *tmp = impl_;
	impl_ = new Infolet_Impl(*tmp);
	--tmp->use_count_;
}

bool Infolet::operator< (const Infolet& info) const
{
	if(start_time() == info.start_time())
		return xtime() < info.xtime();
	return (start_time() < info.start_time());
}

bool Infolet::operator== (const Infolet& info) const
{
	return (start_time() == info.start_time()) &&
		(xtime() == info.xtime());
}
// Var_Info_Impl
Var_Info_Impl::Var_Info_Impl()
	: inited_(false),
	use_count_(1)
{
}
Var_Info_Impl::Var_Info_Impl(const Sub_Header& sub_header)
	: inited_(false),
	use_count_(1)
{
	init(sub_header);
}
Var_Info_Impl::Var_Info_Impl(const Var_Info_Impl& orig)
	:
	ndim_(orig.ndim_),
	name_(orig.name_),
	unit_(orig.unit_),
	description_(orig.description_),
	staggering_(orig.staggering_),
	ordering_(orig.ordering_),
	inited_(orig.inited_),
	use_count_(1)
{
	for(int i = 0; i < 4; ++i)
	{
		start_index_[i] = orig.start_index_[i];
		end_index_[i] = orig.end_index_[i];
	}
	field_pos_.assign(orig.field_pos_.begin(), orig.field_pos_.end());
}

void Var_Info_Impl::init(const Sub_Header& sub_header)
{
	ndim_ = sub_header.ndim();
	for(int i = 0; i < 4; ++i)
	{
		start_index_[i] = sub_header.start_index(i);
		end_index_[i] = sub_header.end_index(i);
	}
	name_ = sub_header.name();
	unit_ = sub_header.unit();
	description_ = sub_header.description();
	staggering_ = sub_header.staggering();
	ordering_ = sub_header.ordering();
	
	inited_ = true;
}

int Var_Info_Impl::field_size() const
{
	return dim_size(0) * dim_size(1) * dim_size(2);
}


// queries
std::string Var_Info_Impl::name() const
{
	return name_;
}

std::string Var_Info_Impl::unit() const
{
	return unit_;
}

std::string Var_Info_Impl::description() const
{
	return description_;
}
	
std::string Var_Info_Impl::ordering_name() const
{
	return ordering_to_string(ordering_);
}

int Var_Info_Impl::ordering() const
{
	return ordering_;
}

int Var_Info_Impl::ndim() const
{
	return ndim_;
}

int Var_Info_Impl::start_index(int dim) const
{
	return start_index_[dim];
}

int Var_Info_Impl::end_index(int dim) const
{
	return end_index_[dim];
}

int Var_Info_Impl::dim_size(int dim) const
{
	return end_index(dim) - start_index(dim) + 1;
}

int Var_Info_Impl::ntime_steps() const
{
	return field_pos_.size();
}

Var_Info_Impl::const_iterator Var_Info_Impl::begin() const
{
	return field_pos_.begin();
}

Var_Info_Impl::const_iterator Var_Info_Impl::end() const
{
	return field_pos_.end();
}

// operation
void Var_Info_Impl::add_record(const Infolet& infolet)
{
	if(!inited_)
	{
		throw exceptions::object_uninitialized("Var_Info");
	}
	std::pair<iterator, iterator> range = std::equal_range(field_pos_.begin(), field_pos_.end(), infolet);
	// means there's only one place to insert the new value.
	// so no element is the same with the new one.
	// or else, do not insert.
	if(range.first == range.second)
	{
		field_pos_.insert(range.first, infolet);
	}
}

void Var_Info_Impl::keep_only_one_record()
{
	if(field_pos_.empty())
		return;
	iterator beg = field_pos_.begin();
	++beg;
	field_pos_.erase(beg, field_pos_.end());
}

// Var_Info
Var_Info::Var_Info()
	: impl_(new Var_Info_Impl())
{
}

Var_Info::Var_Info(const Sub_Header& sub_header)
	: impl_(new Var_Info_Impl(sub_header))
{
}

Var_Info::Var_Info(const Var_Info& orig)
	: impl_(orig.impl_)
{
	++impl_->use_count_;
}

Var_Info::~Var_Info()
{
	if(--impl_->use_count_ == 0)
		delete impl_;
}

Var_Info& Var_Info::operator= (const Var_Info& orig)
{
	++orig.impl_->use_count_;
	if(--impl_->use_count_ == 0)
		delete impl_;
	impl_ = orig.impl_;
}

void Var_Info::on_write()
{
	if(impl_->use_count_ == 1)
		return;
	Var_Info_Impl *tmp = impl_;
	impl_ = new Var_Info_Impl(*tmp);
	--tmp->use_count_;
}

int Var_Info::field_size() const
{
	return impl_->field_size();
}

int Var_Info::total_size() const
{
	return field_size() * ntime_steps();
}

// queries
std::string Var_Info::name() const
{
	return impl_->name();
}

std::string Var_Info::unit() const
{
	return impl_->unit();
}

std::string Var_Info::description() const
{
	return impl_->description();
}
	
std::string Var_Info::ordering_name() const
{
	return impl_->ordering_name();
}

int Var_Info::ordering() const
{
	return impl_->ordering();
}

int Var_Info::ndim() const
{
	return impl_->ndim();
}

int Var_Info::start_index(int dim) const
{
	return impl_->start_index(dim);
}

int Var_Info::end_index(int dim) const
{
	return impl_->end_index(dim);
}

int Var_Info::dim_size(int dim) const
{
	return impl_->dim_size(dim);
}

int Var_Info::ntime_steps() const
{
	return impl_->ntime_steps();
}

Var_Info::const_iterator Var_Info::begin() const
{
	return impl_->begin();
}

Var_Info::const_iterator Var_Info::end() const
{
	return impl_->end();
}

// operation
void Var_Info::add_record(const Infolet& infolet)
{
	on_write();
	impl_->add_record(infolet);
}

void Var_Info::merge(const Var_Info& info)
{
	on_write();
	for(const_iterator iter = info.begin(); iter != info.end(); ++iter)
	{
		add_record(*iter);
	}
}

void Var_Info::keep_only_one_record()
{
	on_write();
	impl_->keep_only_one_record();
}


} // namespace dodomet::mm5
} // namespace dodomet
